﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Data.Common;
using System.Xml;
using System.IO;
using System.Data;
using System.Collections;
using Wintellect.PowerCollections;
using DbSharp.Framework.Storage.IO;
using DbSharp.Framework.CaseFile;
using DbSharp.Framework.DbObject;
using DbSharp.Framework.Driver;



namespace DbSharp.Framework.Storage.XmlStorage
{

    class XmlRecordsetStorage : IRecordsetStorage
    {
        #region Private members

        private List<FieldMetaData> m_ListFieldMetaData = new List<FieldMetaData>();

        /// <summary>
        ///List item just like: FieldName_UpperCase=FieldOutputName
        /// </summary>
        private Dictionary<string, string> m_ListGivenFieldOutputName = new Dictionary<string, string>();

        /// <summary>
        /// List item just like: FieldName_UpperCase=FieldOutputName_UpperCase
        /// </summary>
        private Dictionary<string, string> m_ListActualFieldOutputUpperName = new Dictionary<string, string>();


        private Dictionary<string, string> m_ListFieldNullPlaceholder = new Dictionary<string, string>();


        private ITestDataSource m_DbTestPlugin;
        private OutputSettingAttribute m_OutputSettingAttr;
        private bool m_StarMarkInOutputFieldNames;
        private long m_SavedRowCount;
        private XElement m_DataRootElement;
        private XElement m_StructRootElement;
        private List<XElement> m_ListRecordElement = new List<XElement>();
        #endregion


        #region Private methods


        /// <summary>
        /// Get the predetermined output name for fields
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns>upper-case field output name</returns>
        private string GetGivenFieldOutputUpperName(string fieldName)
        {
            string outputName;
            if (m_StarMarkInOutputFieldNames)
                outputName = fieldName.Trim();
            else
            {
                string fieldUpperName = fieldName.Trim().ToUpper();
                if (m_ListGivenFieldOutputName.ContainsKey(fieldUpperName) == false)
                {
                    //if not found, return empty. it indicates this field will not be output
                    outputName = string.Empty;
                }
                else
                {
                    outputName = m_ListGivenFieldOutputName[fieldUpperName];
                }
            }

            return outputName.ToUpper();
        }


        /// <summary>
        /// get real output name for fields
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        private string GetActualFieldOutputUpperName(string upperFieldName)
        {
            string upperOutputName;
            if (m_ListActualFieldOutputUpperName.ContainsKey(upperFieldName) == false)
            {
                //if not found, return empty.
                upperOutputName = string.Empty;
            }
            else
            {
                upperOutputName = m_ListActualFieldOutputUpperName[upperFieldName];
            }

            return upperOutputName;
        }

        private int GetFieldType(string upperFieldName)
        {
            int result = -1;
            foreach (var item in m_ListFieldMetaData)
            {
                string.Equals(item.ActualOutputUpperName, item.ActualOutputUpperName, StringComparison.Ordinal);
                {
                    result = item.FieldDataType;
                    break;
                }
            }
            return result;
        }




        /// <summary>
        /// try to normalize field output name, includes, 
        ///  1. pick out the fields that need output indeed
        ///  2. uppercase field name
        /// </summary>
        /// <param name="listFieldOutputName"></param>
        /// <param name="listGivenFieldOutputName">Key:Value=UpperFieldName:FieldOutputName </param>
        /// <returns></returns>
        private bool TryNomalizeGivenOutputFieldNames(Dictionary<string, string> listFieldOutputName,
            ref Dictionary<string, string> listGivenFieldOutputName)
        {
            listGivenFieldOutputName.Clear();

            string fieldName;
            string fieldNameUpper;
            string fieldOutputName;
            try
            {
                foreach (var item in listFieldOutputName)
                {
                    fieldName = item.Key.Trim();
                    fieldNameUpper = fieldName.ToUpper();
                    fieldOutputName = item.Value.Trim();
                    if (fieldName.Length == 0)
                        continue;
                    else if (fieldOutputName.Length == 0)
                        fieldOutputName = fieldName;

                    listGivenFieldOutputName.Add(fieldNameUpper, fieldOutputName);
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        private bool NeedUseCData(string fieldValue, int valueType)
        {
            if (m_OutputSettingAttr.WrapStringByCDATA)
                return StorageTextHelper.PossibleNeedUseCData(fieldValue, valueType, m_DbTestPlugin);
            else
                return false;
        }

        private XElement CreateResultsetRowElement(DbDataReader reader)
        {
            /*
<resultsetData>
<row><FIELDNAME1>value1</FIELDNAME1><FIELDNAME2>value2</FIELDNAME2></row>
<row><FIELDNAME1>value1</FIELDNAME1><FIELDNAME2>value2</FIELDNAME2></row>
</resultsetData>
             * VisibleFieldCount FieldCount
*/
            //var q = from i in Enumerable.Range(0, reader.VisibleFieldCount)
            //        let obj = new { FieldName = reader.GetName(i).ToUpper(), Value = reader.GetValue(i).ToString() }
            //        orderby obj.FieldName
            //        select new XElement(obj.FieldName, obj.Value);

            //return new XElement("row", q.ToArray());

            //var q = from i in Enumerable.Range(0, reader.VisibleFieldCount)
            //        let outputFieldName = m_OutputSetting.GetOutputFieldName(reader.GetName(i))
            //        let fieldValue = reader.GetValue(i).ToString()
            //        let obj = new
            //        {
            //            OutputName = outputFieldName,
            //            //make sure that embed string-type fieldvalue by CDATA
            //            Value2 = reader.GetFieldType(i) == typeof(string) ? fieldValue : new XCData(fieldValue)
            //        }
            //        where (!string.IsNullOrEmpty(outputFieldName))
            //        orderby obj.OutputName
            //        //select new XElement(obj.OutputName, obj.Value);
            //        select new XElement(obj.OutputName, obj.Value2);


            var q = from i in Enumerable.Range(0, reader.VisibleFieldCount)
                    let upperFieldName = reader.GetName(i).ToUpper()
                    let outputFieldName = GetActualFieldOutputUpperName(upperFieldName)
                    let fieldType = GetFieldType(outputFieldName)
                    let fieldValue1 = ReplaceNullWithPlaceholder(upperFieldName, reader.GetValue(i))
                    let fieldValue2 = m_DbTestPlugin.FieldPersistenceService.ConvertToString(fieldValue1, fieldType)
                    let fieldValueText3 = StorageTextHelper.EscapeNewlineMark(fieldValue2)
                    let fieldOutputValue = NeedUseCData(fieldValueText3, fieldType) ? (object)(new XCData(fieldValueText3)) : fieldValueText3
                    where (!string.IsNullOrEmpty(outputFieldName))
                    orderby outputFieldName //***** order is must
                    select new XElement(outputFieldName, fieldOutputValue);

            return new XElement("row", q.ToArray());
        }

        private object ReplaceNullWithPlaceholder(string fieldUpperName, object fieldValue)
        {
            if ((fieldValue == null) || (fieldValue is DBNull))
            {
                if (m_ListFieldNullPlaceholder.ContainsKey(fieldUpperName))
                    return m_ListFieldNullPlaceholder[fieldUpperName];
                else
                    return string.Empty;
            }
            else
                return fieldValue;
        }



        private XElement CreateResultsetDataElement(DbDataReader reader)
        {
            return new XElement("resultsetData");
        }



        /// <summary>
        /// initilize m_ListActualFieldOutputUpperName according and incoming dbDataReader and m_ListGivenFieldOutputName
        /// This function can handle the names that are invalid as XElment name
        /// </summary>
        /// <param name="reader"></param>
        private void InitActualOutputFieldMeta(DbDataReader reader)
        {
            m_ListActualFieldOutputUpperName.Clear();
            m_ListFieldMetaData.Clear();

            string fieldUpperName;
            string givenOutputFieldName;
            string actualOutputFieldName;
            for (int i = 0; i < reader.VisibleFieldCount; i++)
            {
                fieldUpperName = reader.GetName(i).ToUpper();
                givenOutputFieldName = GetGivenFieldOutputUpperName(fieldUpperName);
                actualOutputFieldName = givenOutputFieldName;

                if (string.IsNullOrEmpty(actualOutputFieldName) == false)
                {
                    //check the outputFieldName is legal for Xml Element Name
                    try
                    {
                        new XElement(actualOutputFieldName);
                    }
                    catch
                    {
                        actualOutputFieldName = "F" + Math.Abs(actualOutputFieldName.GetHashCode());
                    }

                    //if not in m_ListActualFieldOutputUpperName yet, add it in. 
                    if (!m_ListActualFieldOutputUpperName.ContainsKey(fieldUpperName))
                    {
                        m_ListActualFieldOutputUpperName.Add(fieldUpperName, givenOutputFieldName);


                        //add one FieldMetaData object in m_ListFieldMetaData
                        m_ListFieldMetaData.Add(new FieldMetaData()
                        {
                            FieldUpperName = fieldUpperName,
                            ActualOutputUpperName = actualOutputFieldName,
                            FieldDataType = m_DbTestPlugin.FieldPersistenceService.GetSpecificDbType(reader, i),
                            GivenOutputName = givenOutputFieldName,
                            NullPlaceholder = GetFieldNullPlaceholder(fieldUpperName),
                        });
                    }
                }
            }
        }


        private string GetFieldNullPlaceholder(string fieldUpperName)
        {
            if (m_ListFieldNullPlaceholder.ContainsKey(fieldUpperName))
                return m_ListFieldNullPlaceholder[fieldUpperName];
            else
                return string.Empty;
        }

        private XElement CreateResultsetStructElement(DbDataReader reader)
        {
            /*
<resultsetStructure>
<field fieldName="" dataType=""/>
<field fieldName="" dataType=""/>
<field fieldName="" dataType=""/>
</resultsetStructure>
 */

            var q = from i in Enumerable.Range(0, reader.VisibleFieldCount)
                    let upperFieldName = reader.GetName(i).ToUpper()
                    let outputFieldName = GetActualFieldOutputUpperName(upperFieldName)
                    let obj = new
                    {
                        OutputName = outputFieldName,
                        Type = reader.GetDataTypeName(i)
                    }
                    where (!string.IsNullOrEmpty(outputFieldName))
                    orderby outputFieldName.ToUpper()
                    select new XElement("field",
                             new XAttribute("fieldName", obj.OutputName),
                             new XAttribute("dataType", obj.Type));


            return new XElement("resultsetStructure", q.ToArray());
        }

        #endregion




        public XmlRecordsetStorage(
            ITestDataSource dbTestPlugin,
            OutputSettingAttribute settingAttr,
            Dictionary<string, string> listGivenFieldOutputName,
            Dictionary<string, string> listFieldNullPlaceHolder)
        {
            m_DbTestPlugin = dbTestPlugin;
            m_OutputSettingAttr = settingAttr;

            //set m_ListGivenFieldOutputName
            if ((listGivenFieldOutputName == null) ||
                (TryNomalizeGivenOutputFieldNames(listGivenFieldOutputName, ref m_ListGivenFieldOutputName) == false))
                throw new ArgumentNullException("listGivenFieldOutputName is null or some of items contain null object.");


            //set m_ListFieldNullPlaceholder
            m_ListFieldNullPlaceholder.Clear();
            foreach (var pair in listFieldNullPlaceHolder)
            {
                m_ListFieldNullPlaceholder.Add(pair.Key.Trim().ToUpper(), pair.Value);
            }

            m_StarMarkInOutputFieldNames = m_ListGivenFieldOutputName.Keys.Contains("*");
        }



        #region IRecordsetStorage Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public void Initialize(DbDataReader reader)
        {
            InitActualOutputFieldMeta(reader);

            m_SavedRowCount = 0;
            m_ListRecordElement.Clear();
            m_StructRootElement = CreateResultsetStructElement(reader);
            m_DataRootElement = CreateResultsetDataElement(reader);
        }

        public void SaveRecord(DbDataReader reader)
        {
            m_ListRecordElement.Add(CreateResultsetRowElement(reader));
            m_SavedRowCount++;
        }

        public void Terminate()
        {
            // add all records into DataRootElement
            m_DataRootElement.Add(m_ListRecordElement);
        }



        public object RecordsetContainer
        {
            get { return m_ListRecordElement; }
        }



        public List<string> GetOutputFieldNameList()
        {
            var q = from item in m_ListActualFieldOutputUpperName
                    orderby item.Value ascending
                    select item.Value;
            return q.ToList();
        }


        public List<FieldMetaData> GetOutputFieldMetaDataQueue()
        {
            var q = from item in m_ListFieldMetaData
                    orderby item.ActualOutputUpperName ascending
                    select item;
            return q.ToList();
        }

        //public string ExportToXmlFile()
        //{
        //    RecordsetXElmtTextWriter writer = new RecordsetXElmtTextWriter(m_ListRecordElement,true,m_ExportRecordCount);
        //    StringBuilder sb = new StringBuilder();
        //    sb.Append("<resultset>");
        //    sb.Append(Environment.NewLine);
        //    //get list of row section
        //    sb.Append(writer.StringBuilder.ToString()) ;
        //    sb.Append(Environment.NewLine);
        //    sb.Append("</resultset>");

        //    string xmlFile ;
        //    StorageNameMaker.PrepareExportXmlFileName(out xmlFile);
        //    File.WriteAllText(xmlFile, sb.ToString(), Encoding.UTF8);

        //    //export to excel file
        //    //XmlToExcelWriter excelWriter = new XmlToExcelWriter();
        //    //excelWriter.OutputFileName = xmlFile + ".xls";
        //    //excelWriter.ListRecordRow = m_ListRecordElement;
        //    //excelWriter.SaveToFile();

        //    return xmlFile;  
        //}




        public StorageMediaType StorageMediaType
        {
            get { return StorageMediaType.Xml; }
        }

        #endregion
    }
}
